package away3d.animators.data ;


import away3d.animators.skeleton.Skeleton ;
import away3d.materials.passes.MaterialPassBase ;
import flash.display3D.Context3D;



/**
 * SkeletonAnimation defines an animation type that blends different skeletal poses into a final pose and transforms
 * the geometries' vertices along the skeleton. Each vertex is bound to a number of skeleton joints with a number of
 * weights, which define how the skeleton influences the vertex position.
 *
 * @see away3d.core.animation.skeleton.Skeleton
 * @see away3d.core.animation.skeleton.Joint
 */
class SkeletonAnimation extends AnimationBase
{
    
    
    private var _forceCPU           : Bool ;
    public var usesCPU              : Bool ;
    private var _skeleton           : Skeleton ;
    private var _nullAnimation      : NullAnimation ;
    private var _jointsPerVertex    : Int ;
    
    
    
    /**
     * Creates a SkeletonAnimation instance.
     * @param skeleton The skeleton that's used for this SkeletonAnimation instance.
     * @param jointsPerVertex The amount of joints that can be linked to a vertex.
     */
    public function new( skeleton : Skeleton, ?jointsPerVertex : Int = 4, ?forceCPU : Bool = false )
    {
        
        _forceCPU = _usesCPU    = forceCPU ;
        _skeleton               = skeleton ;
        _jointsPerVertex        = jointsPerVertex ;
        
    }
    
    //TODO: do we need set getters?!
    public var jointsPerVertex( get_jointsPerVertex, set_jointsPerVertex ) : Int ;
    
    /**
     * The amount of joints that can be linked to a vertex.
     */
    public function get_jointsPerVertex() : Int
    {
        
        return _jointsPerVertex ;
        
    }
    
    
    public function set_jointsPerVertex( value : Int ) : Int
    {
        
        _jointsPerVertex = value ;
        return value ;
        
    }
    
    
    public var skeleton ( get_skeleton, set_skeleton ): Skeleton ;
    
    /**
     * The skeleton that's used for this SkeletonAnimation instance.
     */
    public function get_skeleton() : Skeleton
    {
        
        return _skeleton ;
        
    }
    
    
    public function set skeleton( value : Skeleton ) : Skeleton
    {
        
        _skeleton = value ;
        return value ;
        
    }
    
    
    /**
     * The amount of joints in the skeleton.
     */
    public function get numJoints() : Int
    {
        
        return _skeleton.numJoints;
        
    }
    
    
    /**
     * @inheritDoc
     */
    public override function deactivate( context3D: Context3D, pass: MaterialPassBase )
    {
        
        if( _usesCPU ) return ;
        
        var streamOffset = pass.numUsedStreams ;
        
        context3D.setVertexBufferAt( streamOffset, null ) ;
        context3D.setVertexBufferAt( streamOffset + 1, null ) ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    public override function createAnimationState() : AnimationStateBase
    {
        
        return new SkeletonAnimationState( this ) ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    public override function getAGALVertexCode( pass : MaterialPassBase ) : String
    {
        
        var attributes  = pass.getAnimationSourceRegisters() ;
        var len         = attributes.length ;
        
        // if too many bones to fit in the constants, fall back to cpu animation :(
        if( _forceCPU || _jointsPerVertex > 4 || pass.numUsedVertexConstants + _skeleton.numJoints * 3 > 128 )
        {
            
            _usesCPU        = true ;
            _nullAnimation  = new NullAnimation() ;
            
            return _nullAnimation.getAGALVertexCode( pass ) ;
            
        }
        
        var targets         = pass.getAnimationTargetRegisters() ;
        var indexOffset0    = pass.numUsedVertexConstants ;
        var indexOffset1    = indexOffset0 + 1 ;
        var indexOffset2    = indexOffset0 + 2 ;
        var indexStream     = "va" + pass.numUsedStreams ;
        var weightStream    = "va" + ( pass.numUsedStreams + 1 ) ;
        var indices         = [ indexStream + ".x",  indexStream  + ".y", indexStream  + ".z", indexStream  + ".w" ] ;
        var weights         = [ weightStream + ".x", weightStream + ".y", weightStream + ".z", weightStream + ".w" ] ;
        var temp1           = findTempReg( targets ) ;
        var temp2           = findTempReg( targets, temp1 ) ;
        var dot             = "dp4" ;
        var code            = "" ;
        
        for( i in 0...len )
        {
            
            var src = attributes[i] ;
            
            for( j in 0..._jointsPerVertex )
            {
                
                code += dot + " " + temp1 + ".x, " + src + ", vc[" + indices[ j ] + "+" + indexOffset0 + "]       \n" +
                        dot + " " + temp1 + ".y, " + src + ", vc[" + indices[ j ] + "+" + indexOffset1 + "]       \n" +
                        dot + " " + temp1 + ".z, " + src + ", vc[" + indices[ j ] + "+" + indexOffset2 + "]       \n" +
                        "mov " + temp1 + ".w, " + src + ".w     \n" +
                        "mul " + temp1 + ", " + temp1 + ", " + weights[ j ] + "\n";	// apply weight
                
                // add or mov to target. Need to write to a temp reg first, because an output can be a target
                if( j == 0 )
                {
                    
                    code += "mov " + temp2 + ", " + temp1 + "\n";
                    
                }
                else 
                {
                    
                    code += "add " + temp2 + ", " + temp2 + ", " + temp1 + "\n";
                    
                }
                
            }
            
            // switch to dp3 once positions have been transformed, from now on, it should only be vectors instead of points
            dot = "dp3";
            code += "mov " + targets[ i ] + ", " + temp2 + "\n";
            
        }
        
        return code;
        
    }
    
    
    /**
     * Retrieves a temporary register that's still free.
     * @param exclude An array of non-free temporary registers
     * @param excludeAnother An additional register that's not free
     * @return A temporary register that can be used
     */ 
    private function findTempReg( exclude : Array, ?excludeAnother : String = null ) : String
    {
        
        var i   : Int;
        var reg : String;
        
        while( true ) 
        {
            reg = "vt" + Std.string( i );
            if( exclude.indexOf( reg ) == -1 && excludeAnother != reg ) return reg;
            ++i;
        }
        
        // can't be reached
        return null;
    
    }
    
    
}